package jasymca;
/* Jasymca	-	- Symbolic Calculator for Mobile Devices
 This version is written for J2ME, CLDC 1.1,  MIDP 2, JSR 75
 or J2SE


 Copyright (C) 2006 - Helmut Dersch  der@hs-furtwangen.de

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2, or (at your option)
 any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/*------------------------------------------------------------*/

import java.io.*;

///////// Start J2SE-only +

public class Jasymca {

  /*
   * // Start interactive session public static void main(String args[]){ // get
   * name of startupfile String s = System.getProperty("JASYMCA_RC");
   * if(s!=null) JasymcaRC=s; // Start interactive session new
   * Jasymca().start(System.in, System.out); }
   */

  static String JasymcaRC = "Jasymca.rc";

  // Evaluate prefix expression
  // if canon is set: eval to canonical expression (Algebraic)
  // Otherwise evaluate only numbers, result may be list
  protected static Object evalPrefix(Object x, boolean canon, Environment env)
      throws ParseException, JasymcaException {
    if (x == null)
      return "";
    if (x instanceof Algebraic)
      return x;
    if (x instanceof String) {
      Object val = env == null ? null : env.getValue((String) x);
      if (val != null)
        return evalPrefix(val, canon, env);
      return canon ? new Polynomial(new SimpleVariable((String) x)) : x;
    }
    if (x instanceof Pair && Lisp.car(x) instanceof String) {
      String id = (String) Lisp.car(x);
      x = Lisp.cdr(x);
      // id might be Vektor reference
      Object val = env == null ? null : env.getValue(id);

      if (val != null && val instanceof Vektor) {
        Object idv = evalPrefix(Lisp.car(x), canon, env);
        if (idv instanceof Vektor && ((Vektor) idv).coord.length == 1) {
          Algebraic idx = ((Vektor) idv).coord[0];
          if (idx instanceof Zahl)
            return ((Vektor) val).komp(((Zahl) idx).intval());
        }
      }
      // or id is Lambda operator
      if (!(val instanceof Lambda))
        if (!canon)
          return Lisp.list(id, evalPrefix(Lisp.car(x), canon, env));
        else
          throw new JasymcaException("Can not evaluate to algebraic.");
      Lambda f = (Lambda) val;

      // If f is a general command, supply args unevaluated
      if (!(f instanceof LambdaAlgebraic))
        return f.lambda(x);
      // Otherwise, evaluate args depending on canon
      // Algebraic function f(x,y,z,...)
      // Supplied argument to lambda must be Algebraic
      // if( !",".equals(Lisp.car(x))){ // Single argument function
      Object args = null;
      while (x instanceof Pair) {
        args = Lisp.cons(evalPrefix(Lisp.car(x), canon, env), args);
        x = Lisp.cdr(x);
      }
      args = Lisp.reverse(args);
      // at this point args is a prefix expression
      if (Lisp.algebraicq(args))
        return f.lambda(args);
      if (canon)
        throw new JasymcaException("Can not evaluate to algebraic.");
      return Lisp.list(id, args);

    }
    throw new ParseException("Not a legal expression:" + x);
  }

  // Insert x in rule "f(x)" and evaluate expression to Algebraic
  protected static Algebraic evalx(String rule, Algebraic x, Environment env)
      throws JasymcaException {
    try {
      Object prefix = Lisp.change(Lisp.compile_rule(rule), Lisp.list(Lisp.cons(
          "x", x)));
      return (Algebraic) evalPrefix(prefix, true, env);
    } catch (Exception e) {
      throw new JasymcaException("Could not evaluate expression " + rule + ": "
          + e.toString());
    }
  }

  // ////// End J2SE-only -

  // ////// Start J2ME-only +
  /*
   * import kjava.io.*; import kjava.util.*; import jsystem.*; import
   * jsystem.JConsole.*;
   * 
   * 
   * import javax.microedition.lcdui.*; import javax.microedition.rms.*;
   * 
   * public class Jasymca extends javax.microedition.midlet.MIDlet{
   * 
   * static InputStream getFileInputStream( String fname) throws IOException{
   * return new kjava.io.FileInputStream( fname ); }
   * 
   * static OutputStream getFileOutputStream( String fname, boolean append)
   * throws IOException{ return new kjava.io.FileOutputStream( fname, append );
   * }
   * 
   * static String JasymcaRC = "vfs/Jasymca.rc";
   * 
   * 
   * public void startApp(){ JSystem.initConsole(Display.getDisplay(this));
   * FileHandler[] fh = { new TextEdit() }; JSystem.browser = new FileBrowser(
   * JSystem.display, JSystem.console, fh);
   * 
   * JSystem.console.setTitle("Jasymca"); JSystem.showConsole();
   * JSystem.console.stdout.println("Jasymca v. 1.01");
   * 
   * JSystem.out = JSystem.console.stdout; JSystem.err = JSystem.console.stdout;
   * JSystem.in = JSystem.console.stdin;
   * 
   * start(JSystem.in, JSystem.out); }
   */
  // ////// End J2ME-only -

  static InputStream getFileInputStream(String fname) throws IOException {
    return new FileInputStream(fname);
  }
  static OutputStream getFileOutputStream(String fname, boolean append)
      throws IOException {
    return new FileOutputStream(fname, append);
  }

  // /

  // Jasymca = Java Symbolic Calculator
  // Syntax loosely related to GNU-Maxima

  // Internal Design:
  // Expressions are Lisp-lists in prefix notation
  // Elements are either parseable Lists or Algebraics
  // Variables are one of
  // --- SimpleVariable = name (String)
  // --- FunctionVariable = function (LambdaAlgebraic) + Argument (Algebraic)
  // Algebraics are one of
  // --- Zahl.Unexakt ---> double complex
  // --- Zahl.Exakt ---> BigInteger rational complex
  // --- Polynomial ---> Coefficients (Algebraic) + Variable
  // --- Polynomial.Constant ---> Coefficients (Algebraic) + immutable Variable
  // --- Rational ---> Nominator (Algebraic) + Denominator (Polynomial)
  // --- Vektor ---> Components (Algebraic)
  // --- Matrix ---> Vektor of Components

  // Environment for variables, functions
  // and operators
  // Stored by name, case-insensitive
  private final Environment env;

  InputStream is;

  // Parse constant, indicates end of input
  static String EXIT = "#!exit";

  public Jasymca() {
    // Setup environment
    env = new Environment();
    // Export environment: This should be removed one day
    Lambda.env = env;
    env.putValue("pi", Zahl.PI);
    env.putValue("ratepsilon", new Unexakt(2.0e-8));
    env.putValue("algepsilon", new Unexakt(1.0e-8));
    env.putValue("rombergit", new Unexakt(11));
    env.putValue("rombergtol", new Unexakt(1.0e-4));
    env.putValue("+", new Add());
    env.putValue("-", new Sub());
    env.putValue("*", new Mult());
    env.putValue("/", new Div());
    env.putValue("^", new Pow());
    env.putValue(":", new Assign());
    env.putValue("&", new FunctionDefine());
    env.putValue(",", new Comma());
    env.putValue("#", new CreateVector());
    env.putValue("!", new Fact());

    // More initialization
    Zahl.init();

  }

  // is x binary operand
  boolean binaryq(String op) {
    switch (op.charAt(0)) {
      case '+' :
      case '-' :
      case '*' :
      case '^' :
      case '/' :
      case ',' :
        return true;
      default :
        return false;
    }
  }

  public void destroyApp(boolean a) {
  }

  // Parsing proceeds in these steps:
  // (1) Parse Tokens to Objects -------- read()
  // (2) Convert Infix to Prefix -------- in_pr()
  // (3) Evaluate prefix expression -------- evalPrefix()
  Object eval(String s) throws ParseException {
    Object o;
    try {
      o = Lisp.expandExp(Lisp.expandFundef(Lisp.read(s)));
      return evalPrefix(Lisp.in_pr(o), false, env);
    } catch (Exception e) {
      throw new ParseException(e.toString());
    }
  }

  String formatExpression(Object expr) {
    if (expr instanceof Algebraic)
      return expr.toString();
    return infix(expr);
  }

  // Convert prefix expression to infix string
  private String infix(Object x) {
    if (Lisp.length(x) == 0)
      return x.toString();
    if (Lisp.length(x) == 1)
      return Lisp.car(x).toString();
    String op = (String) Lisp.car(x);
    Object args = Lisp.cdr(x);
    if (binaryq(op))
      if (Lisp.length(args) == 1)
        return "(" + op + " " + infix(Lisp.car(args)) + ")";
      else
        return "(" + infix(Lisp.car(args)) + op
            + infix(Lisp.car(Lisp.cdr(args))) + ")";
    return op + "(" + infix(Lisp.list2atom(args)) + ")";
  }

  public void pauseApp() {
  }

  // Read everything until ';' or EOF
  String readLine(InputStream in) {
    StringBuffer s = new StringBuffer();
    try {
      int c;
      while ((c = in.read()) != -1 && c != ';')
        s.append((char) c);
    } catch (Exception e) {
    }
    return s.toString();
  }

  public void start(InputStream is, PrintStream ps) {
    this.is = is;
    // Read startup file
    try {
      new LambdaLOADFILE().lambda(Lisp.list(JasymcaRC));
    } catch (Exception e) {
    }

    // Counter for Line numbers
    int i = 1;

    // Read/eval/print loop
    while (true) {
      ps.print("(In" + i + ") "); // Prompt
      try {
        String s = readLine(is);
        Object expr = eval(s);
        if (expr == EXIT) {
          ps.println("\nGoodbye.");
          break;
        }
        String ans = formatExpression(expr);
        ps.println("(Out" + i + ")     " + ans);
        env.putValue("Out" + i, expr); // Save expression
        i++;
      } catch (ParseException e) {
        ps.println("\n" + e);
      }
    }
  }

}
